FFHS DAS Data Science
Semesterarbeit FS20
Iwan Imsand
Statistische Datenanalyse, StatDa
Nach der ersten Präsenz
In diesem Kapitel wird eine deskriptive Analyse für den gewählten Datensatz durchgeführt.
Folgende Quellen wurden benutzt:
Die Datenbeschaffung, Bereinigung und Zusammenführung wurde in den Notebooks 0_*.ipynb durchgeführt.
Als Grundlage für alle nachfolgenden Analysen, dienen die folgenden Daten:
| Dateiname | Beschreibung |
|---|---|
samples_5000_201910-citibike-tripweather-data.parquet |
Enthält 5000 zufällig gewählte Stichproben aus dem Monat Oktober des Jahres 2019. |
summary-daily-subscribers_only-citibike-tripweather.parquet |
Enthält eine aggregierte Zusammenfassung pro Tag über alle Jahre. Es wurden nur Jahresmitglieder berücksichtigt! |
summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet |
Enthält eine nach Geschlecht gruppierte und aggregierte Zusammenfassung pro Tag über alle Jahre. Es wurden nur Jahresmitglieder berücksichtigt! |
Alle Dateien befinden sich im Pfad ./../data/citibike/tripdata/.
import os
import pandas as pd
import numpy as np
import math
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.dates as mdates
from matplotlib.lines import Line2D
import seaborn as sns
from datetime import datetime
from dateutil import tz
import sidetable
from plotnine import *
%matplotlib inline
# Needed for correct time plots
matplotlib.rcParams['timezone'] = 'US/Eastern'
pd.options.display.float_format = '{:.5f}'.format
path = './../data/citibike/tripdata'
SEED=4242123 # Needed when using sample
def show_hist(data, xlabel, ylabel, title, nbins=0, log=False, figsize=(15,2)):
'''
Shows a histogram of data with some other informations.
'''
fig = plt.figure(figsize=figsize)
if nbins == 0:
if data.count() <= 100:
nbins = int(math.sqrt(data.count()))
elif data.count() <= 1000:
nbins = 15
else:
nbins = 20
ax = plt.subplot()
n, bins, patches = ax.hist(data, bins=nbins, log=log)
ax.set_xlabel(xlabel)
if log:
ax.set_ylabel(ylabel + '(log)')
else:
ax.set_ylabel(ylabel + '(lin)')
ax.set_title(title)
ax.set_axisbelow(False)
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
plt.show()
print('Info Histogram: [ Klassen: {}, Breite: {:.5f}, Skew: {:.5f}, Kurt: {:.5f} ]'.format(
len(n),
np.diff(bins)[0],
data.skew(),
data.kurt())
)
return n, bins, patches
def show_box(data, xlabel, title, ylabel='', vert=False, labels=[''], log=False, figsize=(15,2)):
'''
Shows a boxplot of data.
'''
fig = plt.figure(figsize=figsize)
ax = plt.subplot()
boxplot = ax.boxplot(data, vert=vert, showmeans=True, labels=labels, widths=0.7)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.set_title(title)
if log:
ax.set_xscale('log')
ax.set_axisbelow(True)
#ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
#plt.tight_layout()
plt.show()
return boxplot
samples_5000_201910-citibike-tripweather-data.parquet¶df_oct2019 = pd.read_parquet(os.path.join(path, 'samples_5000_201910-citibike-tripweather-data.parquet'))
df_oct2019.head().T
Die Merkmale die in der Datei samples_5000_201910-citibike-tripweather-data.parquet enthalten sind, werden in folgender Tabelle beschrieben.
| Statistische Einheit | Merkmal | Merkmalsausprägung / Beispiel | Skalenniveau | Kontinuität | Beschreibung |
|---|---|---|---|---|---|
| Trip | dt_utc | 2019-10-01 400:00:05+00:00 | Intervall | stetig | Zeitpunkt an welchem der Trip gestartet wurde in 'Koordinierter Weltzeit' (UTC) |
| Trip | Trip Duration | 100 Sekunden | Verhältnis | stetig | Dauer des Trips in Sekunden |
| Trip | Start Time | 2019-01-01 00:01:05-04:00 | Intervall | stetig | Zeitpunkt an welchem der Trip gestartet wurde mit Zeitzone US/Eastern |
| Trip | Stop Time | 2019-01-01 00:07:07-04:00 | Intervall | stetig | Zeitpunkt an welchem der Trip beendet wurde mit Zeitzone US/Eastern |
| Trip | Linear Distance | 1.55503 | Verhältnis | stetig | Distanz der Luftlinie zwischen Start und Stop Station in km (Berechnet mit haversine) |
| Station | Start Station ID | 3160 | Nominal | diskret | Eindeutige Identifikation der Station an welcher der Trip gestartet wurde |
| Station | Start Station Name | Central Park West & W 76 St | Nominal | diskret | Name der Startstation |
| Station | Start Station Latitude | 40.778968 | Intervall | stetig | Breitengrad der Startstation |
| Station | Start Station Longitude | -73.973747 | Intervall | stetig | Längengrad der Startstation |
| Station | End Station ID | 3283 | Nominal | diskret | Eindeutige Identifikation der der Station an welcher der Trip beendet wurde |
| Station | End Station Name | W 89 St & Columbus Ave | Nominal | diskret | Name der Endstation |
| Station | End Station Latitude | 40.788221 | Intervall | stetig | Breitengrad der Endstation |
| Station | End Station Longitude | -74.00597 | Intervall | stetig | Längengrad der Endstation |
| Bike | Bike ID | 15839 | Nominal | diskret | Eindeutige Identifikation des Bikes |
| User | User Type | Subscriber | Nominal | diskret | Benutzertyp: Customers=24-hour pass oder 3-day pass; Subscribers=Annual Member |
| User | Birth Year | 1971 | Intervall | diskret | Geburtsjahr des Benutzers |
| User | Gender | 1 | Nominal | diskret | Geschlecht des Benutzers: 0=unknown; 1=male; 2=female |
| User | Age 2020 | 28 | Verhältnis | diskret | Alter des Benutzers im Jahr 2020 in Jahren |
| Temperature | temp | 17.52 | Intervall | stetig | Temperatur in Grad Celsius |
| Air | pressure | 1023 | Intervall | stetig | Luftdruck (auf Meereshöhe), hPa |
| Air | humidity | 75 | Intervall | stetig | Luftfeuchtigkeit in % |
| Wind | wind_speed | 5.27 | Verhältnis | stetig | Windgeschwindigkeit in m/s |
| Rainfall | rain_1h | 0.25 | Verhältnis | stetig | Regenmenge der letzten Stunde in mm |
| Snowfall | snow_1h | 0.00 | Verhältnis | stetig | Schneemenge der letzten Stunde in mm |
| Clouds | clouds_all | 100 | Intervall | stetig | Bewölkung in % |
| Weather | weather_id | 500 | Nominal | diskret | Code für die Wetterbedingung (Aufgelistet unter https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) |
| Weather | weather_main | Rain | Nominal | diskret | Gruppe der Wetterbedingung (z.B. Rain,Clear,Clouds, für weitere: https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) |
| Weather | weather_description | light rain | Nominal | diskret | Beschreibung der Wetterbedingung (z.B. light rain, sky is clear, few clouds, für weitere https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) |
Damit eine Interpretation einiger Merkmale in der Analyse einfacher wird, werden zusätzliche Spalten in anderen Einheiten hinzugefügt.
df_oct2019.insert(loc=2, column='Trip Duration (min)', value=df_oct2019['Trip Duration'] / 60)
df_oct2019.insert(loc=3, column='Trip Duration (h)', value=df_oct2019['Trip Duration'] / 60 / 60)
df_oct2019.insert(loc=4, column='Trip Duration (d)', value=df_oct2019['Trip Duration'] / 60 / 60 / 24)
Folgende Spalten wurden ergänzt:
[col for col in df_oct2019.columns if col.endswith(')') ]
Untersuchungen zur Anzahl der Trips im Oktober 2019.
df_oct2019_idx = df_oct2019.set_index(pd.DatetimeIndex(data=df_oct2019['Start Time'].dt.date, name='Date', tz=tz.gettz('US/Eastern')))
df_oct2019_grouped = df_oct2019_idx.groupby(by=['Date'])[['Trip Duration']].agg('count').rename({'Trip Duration': 'Trip count'}, axis=1)
df_oct2019_grouped = df_oct2019_grouped.resample('1D').ffill()
df_oct2019_grouped.describe()
days_in_mean_pm_std = df_oct2019_grouped[
(df_oct2019_grouped > (df_oct2019_grouped.mean() - df_oct2019_grouped.std())) &
(df_oct2019_grouped < (df_oct2019_grouped.mean() + df_oct2019_grouped.std()))
].count()
days_in_mean_pm_std_percent = days_in_mean_pm_std / df_oct2019_grouped.count()
print('Anzahl der Daten, die im Bereich mean+-std liegen: {:.2f}% ({} Tage)'.format(days_in_mean_pm_std_percent[0]*100, days_in_mean_pm_std[0]))
Folgendes ist aus den Zahlen herauszulesen:
n, bins, patches = show_hist(df_oct2019_grouped['Trip count'], 'Anzahl Trips', 'Anzahl (linear)', 'Trips im Oktober 2019')
print('Grenzen: {}, Anzahl: {}'.format(bins, n))
Im Histogramm ist ersichtlich, dass es Tage gab, an welchen nur 60-90 Trips gefahren wurden, dies sieht man am Balken ganz rechts. Es gab nur 2 Tage im Monat Oktober, an welchen die Anzahl der Trips unter 91 fiel (die Obergrenze der ersten Klasse liegt bei 90.4, daher sind 90 Trips noch in der ersten Klasse enthalten). An 13 Tagen wurden zwischen 182 und 212 Trips gefahren, was an dem Balken ganz rechts abgelesen werden kann.
Auch zu erwähnen sind die Zahlen Skew und Kurt:
boxplot = show_box(df_oct2019_grouped['Trip count'], 'Anzahl Trips', 'Trips im Oktober 2019', labels=['Trip count'])
Der Boxplot zeigt das gleiche Bild das schon oben alles erkannt wurde, sofort ersichtlich ist hier aber (die genauen Zahlen wurden von oben abgelesen):
Es wird noch ein Balkendiagramm mit der Anzahl Trips über den ganzen Monat erstellt. Auch wird das Wochenende farblich markiert.
df_oct2019_grouped_subscriber = df_oct2019_idx[df_oct2019_idx['User Type'] == 'Subscriber'].groupby(by=['Date'])[['Trip Duration']].agg('count').rename({'Trip Duration': 'Trip count'}, axis=1)
df_oct2019_grouped_subscriber = df_oct2019_grouped_subscriber.resample('1D').ffill()
df_oct2019_grouped_customer = df_oct2019_idx[df_oct2019_idx['User Type'] == 'Customer'].groupby(by=['Date'])[['Trip Duration']].agg('count').rename({'Trip Duration': 'Trip count'}, axis=1)
df_oct2019_grouped_customer = df_oct2019_grouped_customer.resample('1D').ffill()
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_oct2019_grouped.plot(ax=ax, y='Trip count', kind='bar', color=df_oct2019_grouped.index.map(lambda date: 'darkblue' if date.weekday() in [5, 6] else 'royalblue'))
ax.set_xlabel('Tag')
ax.set_ylabel('Anzahl Trips')
ax.set_title('Trips im Oktober 2019')
ax.yaxis.grid(linestyle='dashed')
ax.set_xticklabels(df_oct2019_grouped.index.day)
custom_lines = [Line2D([0], [0], color='royalblue', lw=4),
Line2D([0], [0], color='darkblue', lw=4)]
ax.legend(custom_lines, ['Workday', 'Weekend'])
#plt.tight_layout()
plt.show()
Es gibt am 20. Oktober und am 27. Oktober einen Einbruch der Anzahl Trips, d.h. es sind an beiden Tagen knapp über 50 Trips gemacht worden. An den anderen Tagen sind immer über 100 Trips vorhanden. Die zwei Tage, mit den wenigsten Trips, sind Sonntage. Es fällt auf, dass immer bei einem Arbeitstag die Anzhal der Trips knapp über 100 ist, an anderen Tagen aber höher.
Eine weitere Grafik soll die Anzahl der Trips gruppiert nach User Type zeigen.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
ax.plot_date(df_oct2019_grouped.index, df_oct2019_grouped['Trip count'], marker='o', ls='-', label='Gesamt')
ax.plot_date(df_oct2019_grouped_subscriber.index, df_oct2019_grouped_subscriber['Trip count'], marker='o', ls='-', label='Subscriber')
ax.plot_date(df_oct2019_grouped_customer.index, df_oct2019_grouped_customer['Trip count'], marker='o', ls='-', label='Customer')
ax.plot
ax.set_xlabel('Tag')
ax.set_ylabel('Anzahl Trips')
ax.set_title('Trips im Oktober 2019')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d"))
ax.set_ylim(0, 250)
ax.set_xlim([datetime(2019, 10, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 10, 31, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])
for i, label in enumerate(ax.get_xticklabels()):
if df_oct2019_grouped.index[i].date().weekday() in [5,6]:
label.set_color('red')
ax.legend()
#plt.tight_layout()
plt.show()
Hier sieht man, dass erheblich mehr Trips durch Subscriber gefahren werden als durch Customer. Die rot gekennzeichneten Tage in der x-Achse sind Wochenenden, also Samstage und Sonntage.
Ein paar Untersuchungen zu User Type und Gender.
df_oct2019[['User Type', 'Gender']].describe()
Es ist ersichtlich, dass
Anzahl der Trips, gruppiert nach User Type.
trip_count_by_user_type = df_oct2019.groupby(by=['User Type'])['Trip Duration'].agg('count')
trip_count_by_user_type.name = 'Trip Counts'
trip_count_by_user_type
712 Trips wurden von Customern gefahren, 4288 von Subscribern.
Anzahl der Trips, gruppiert nach User Type und Gender.
trip_count_by_user_type_and_gender = df_oct2019.groupby(by=['User Type', 'Gender'])['Trip Duration'].agg('count')
trip_count_by_user_type_and_gender.name = 'Trip Counts'
trip_count_by_user_type_and_gender
Es wurden
Die Zahlen werden auch noch graphisch aufbereitet.
def func(pct, allvals):
idx = np.argwhere(np.round(allvals / allvals.sum() * 100, 3).values == np.round(pct, 2))[0][0]
absolute = int(allvals[idx])
return "{:.1f}%\n({:d})".format(pct, absolute)
def func_inner(pct, allvals):
idx = np.argwhere(np.round(allvals / allvals.sum() * 100, 3).values == np.round(pct, 2))[0][0]
if idx <=2:
pct_new = allvals[idx] / allvals[0:3].sum() * 100
absolute = int(allvals[idx])
else:
pct_new = allvals[idx] / allvals[3:6].sum() * 100
absolute = int(pct_new/100.*np.sum(allvals))
return "{:.1f}% ({:d})".format(pct_new, absolute)
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(30, 15))
size = 0.3
cmap = plt.get_cmap("tab20c")
outer_colors = cmap(np.arange(3)*4)
inner_colors = cmap(np.array([1, 2, 3, 5, 6, 7]))
outer_labels = ['Customer', 'Subscriber']
inner_labels = ['unknown', 'male', 'female', 'unknown', 'male', 'female']
inner_labels_abs = ['unknown Customers', 'male Customers', 'female Customers', 'unknown Subscribers', 'male Subscribers', 'female Subscribers']
ax[0].pie(trip_count_by_user_type, autopct=lambda pct: func(pct, trip_count_by_user_type), labels=outer_labels, radius=1, colors=outer_colors,
wedgeprops=dict(width=size, edgecolor='w'), pctdistance=0.85, labeldistance=1.05)
ax[0].pie(trip_count_by_user_type_and_gender, autopct=lambda pct: func_inner(pct, trip_count_by_user_type_and_gender), labels=inner_labels, radius=1-size, colors=inner_colors,
wedgeprops=dict(width=size, edgecolor='w'), pctdistance=0.78, labeldistance=0.39, rotatelabels=True)
ax[1].pie(trip_count_by_user_type_and_gender, autopct=lambda pct: func(pct, trip_count_by_user_type_and_gender), labels=inner_labels_abs, radius=1-size, colors=inner_colors,
wedgeprops=dict(width=size, edgecolor='w'), pctdistance=0.78, labeldistance=1.05, rotatelabels=True)
ax[0].set(aspect="equal", title='Anteil `User Type` und `Gender`')
ax[1].set(aspect="equal", title='Anteil `User Type` und `Gender`')
#plt.tight_layout()
plt.show()
In der linken Grafik ist folgendes ersichtlich:
Die rechte Grafik stellt das innere Diagramm einfach nochmal dar, die Zahlen beziehen sich hier aber auf die gesamte Anzahl der Trips.
Untersuchungen zur Dauer der Trips.
df_oct2019[[col for col in df_oct2019.columns if col.startswith('Trip Duration')]].describe(percentiles=[0.05, .25, .5, .75, .95])
trip_duration_in_mean_pm_std = df_oct2019[
(df_oct2019['Trip Duration'] > (df_oct2019['Trip Duration'].mean() - df_oct2019['Trip Duration'].std())) &
(df_oct2019['Trip Duration'] < (df_oct2019['Trip Duration'].mean() + df_oct2019['Trip Duration'].std()))
]['Trip Duration'].count()
trip_duration_in_mean_pm_std_percent = trip_duration_in_mean_pm_std / df_oct2019['Trip Duration'].count()
print('Anzahl der Beobachtungen, die im Bereich mean+-std liegen: {:.2f}% ({} Trips)'.format(trip_duration_in_mean_pm_std_percent*100, trip_duration_in_mean_pm_std))
Folgende Zahlen sind erwähnenswert:
Das der Durchschnitt 5.3 Minuten über dem Median liegt, ist wohl den ganz langen Trips, wie z.B. dem Ausreisser von 4.4 Tagen, zuzuschreiben.
_ = show_hist(df_oct2019['Trip Duration (min)'], 'min', 'Anzahl', 'Trip Duration im Oktober 2019')
Es scheint nur eine Klasse nahe bei null zu geben. Die Zahlen oben haben schon gezeigt das 95% der Werte unter 33.1 Minuten liegen.
Auch sind die Daten schief und wir haben eine starke Wölbung:
Auch ein Boxplot zeigt, dass die Werte konzentriert in der Nähe von 0 liegen.
_ = show_box(df_oct2019['Trip Duration (min)'], 'min', 'Trip Duration im Oktober 2019', labels=['Trip Duration'])
Damit die Werte rechts auch ersichtlich werden, wird das Histogramm noch mit logarithmischer y-Achse erstellt.
_ = show_hist(df_oct2019['Trip Duration (min)'], 'min', 'Anzahl', 'Trip Duration im Oktober 2019', log=True)
Ab ca. 600 Minuten gibt quasi keine Klassen mehr. Da nur 5% der Werte (d.h. genau $5000 * 0.05 = 250$ Werte) über 33.1 Minuten liegen, wird für die Übersichtlichkeit noch ein Plot mit den Trips, die kürzer als 33.1 Minuten sind, erstellt.
df_oct_2019_trip_duration_under_33_1min = df_oct2019[df_oct2019['Trip Duration (min)'] < 33.1]
_ = show_hist(df_oct_2019_trip_duration_under_33_1min['Trip Duration (min)'], 'min', 'Anzahl', 'Trip Duration < 33.1min im Oktober 2019 ()')
Das Histogramm sieht schon viel besser aus und die Ausreisser wurden entfernt.
Auch sind die Daten nun weniger schief, d.h. moderat schief und die Häufung befindet sich rechts, und wir haben nun auch eine mittlere Wölbung.
Auch der Boxplot zeigt nun ein besseres Bild der Beobachtungen.
_ = show_box(df_oct_2019_trip_duration_under_33_1min['Trip Duration (min)'], 'min', 'Trip Duration im Oktober 2019', labels=['Trip Duration'])
Und dazu auch noch die Tabelle mit einer Beschreibung der Werte.
df_oct_2019_trip_duration_under_33_1min[[col for col in df_oct2019.columns if col.startswith('Trip Duration')]].describe(percentiles=[0.05, .25, .5, .75, .95])
trip_duration_in_mean_pm_std_33_1min = df_oct_2019_trip_duration_under_33_1min[
(df_oct_2019_trip_duration_under_33_1min['Trip Duration'] > (df_oct_2019_trip_duration_under_33_1min['Trip Duration'].mean() - df_oct_2019_trip_duration_under_33_1min['Trip Duration'].std())) &
(df_oct_2019_trip_duration_under_33_1min['Trip Duration'] < (df_oct_2019_trip_duration_under_33_1min['Trip Duration'].mean() + df_oct_2019_trip_duration_under_33_1min['Trip Duration'].std()))
]['Trip Duration'].count()
trip_duration_in_mean_pm_std_percent_33_1min = trip_duration_in_mean_pm_std_33_1min / df_oct_2019_trip_duration_under_33_1min['Trip Duration'].count()
print('Anzahl der Beobachtungen, die im Bereich mean+-std liegen: {:.2f}% ({} Trips)'.format(trip_duration_in_mean_pm_std_percent_33_1min*100, trip_duration_in_mean_pm_std_33_1min))
Gegenüber den anfänglichen Zahlen mit allen Ausreissern, sind nun folgende Änderungen durch Entfernen der Ausreisser ersichtlich:
Die Bikes werden eher nur sehr kurze Zeit genutzt. Dies hängt vermutlich mit dem Preismodell zusammen, bei welchem 30 minuten pro Trip für Customers gratis sind. Bei einem Subscriber sind 45 Minuten gratis. Die einzelnen Gruppen werden noch in einem Boxplot miteinander verglichen.
def get_data_for_boxplot_duration(max_duration_hours = np.Inf):
df = df_oct2019[df_oct2019['Trip Duration (h)'] <= max_duration_hours]
data = [
df[df['User Type'] == 'Customer']['Trip Duration (min)'],
df[df['User Type'] == 'Subscriber']['Trip Duration (min)'],
df[df['Gender'] == '0']['Trip Duration (min)'],
df[df['Gender'] == '1']['Trip Duration (min)'],
df[df['Gender'] == '2']['Trip Duration (min)'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '0')]['Trip Duration (min)'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '1')]['Trip Duration (min)'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '2')]['Trip Duration (min)'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '0')]['Trip Duration (min)'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '1')]['Trip Duration (min)'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '2')]['Trip Duration (min)'],
]
labels = [
'Customers',
'Subscribers',
'Gender Unknown',
'Male',
'Women',
'Customers & Gender Unknown',
'Customers & Male',
'Customers & Woman',
'Subscribers & Gender Unknown',
'Subscribers & Male',
'Subscribers & Woman',
]
return {'data': data, 'labels': labels}
fig, ax = plt.subplots(figsize=(30, 10))
dict_data = get_data_for_boxplot_duration(1)
_ = ax.boxplot(dict_data['data'], vert=True, showmeans=True, labels=dict_data['labels'], showfliers=False)
ax.set_ylabel('min')
ax.set_title('Vergleich Trip Duration (nur Trips <= 1h)')
ax.set_axisbelow(True)
ax.yaxis.grid(linestyle='dashed')
#plt.tight_layout()
plt.show()
Folgendes fällt hier auf:
Obwohl ein Subscriber 45 Minuten pro Trip gratis fahren könnte, verwendet dieser die Bikes weniger lange als ein Customer mit nur 30 Minuten inklusive. Es wird vermutet, dass Customer mehrheitlich Touristen sind, welche Sightseeing betreiben und Subscriber in New York leben und das Bike nutzen, um z.B. zur Arbeit zu fahren.
Ein paar Untersuchungen zu Age 2020.
df_oct2019[['Age 2020']].describe(percentiles=[0.05, .25, .5, .75, .95])
Erkenntnisse aus der Übersicht:
print('Anzahl unterschiedlicher Werte in "Age 2020": {}'.format(len(df_oct2019['Age 2020'].unique())))
Zwar ist Age 2020 diskret, aber da es doch 67 unterschiedliche Werte hat, wird das Merkmal einfach mal als quasi-stetig behandelt und in einem Histogramm visualisiert.
_ = show_hist(df_oct2019['Age 2020'], 'Jahre', 'Anzahl', 'Age 2020 im Oktober 2019')
In der Nähe der Klasse mit 30 Jahren und der Klasse mit 50 Jahren gibt es zwei Anhäufungen. Die meisten Nutzer sind also etwa 30 oder 50 Jahre alt. Rechts scheint es noch ein paar Ausreisser zu geben, diese wären dann über 80 Jahre alt. Um diese auch noch zu sehen, hier das Histogramm nochmals mit logarithmischer y-Achse.
_ = show_hist(df_oct2019['Age 2020'], 'Jahre', 'Anzahl', 'Age 2020 im Oktober 2019', log=True)
Das Histogramm zeigt, dass die meisten unter 80 Jahre alt sind, darüber gibt es nur wenige Klassen. Die beiden Klassen ab 120 Jahre sind sehr unrealistisch und hier wurden vermutlich falsche Angaben gemacht.
_ = show_box(df_oct2019['Age 2020'], 'Jahre', 'Alter im Jahr 2020', labels=['Age 2020'])
Im Boxplot ist ersichtlich,
def get_data_for_boxplot_age2020(max_age = np.Inf):
df = df_oct2019[df_oct2019['Age 2020'] <= max_age]
data = [
df[df['User Type'] == 'Customer']['Age 2020'],
df[df['User Type'] == 'Subscriber']['Age 2020'],
df[df['Gender'] == '0']['Age 2020'],
df[df['Gender'] == '1']['Age 2020'],
df[df['Gender'] == '2']['Age 2020'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '0')]['Age 2020'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '1')]['Age 2020'],
df[(df['User Type'] == 'Customer') & (df['Gender'] == '2')]['Age 2020'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '0')]['Age 2020'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '1')]['Age 2020'],
df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '2')]['Age 2020'],
]
labels = [
'Customers',
'Subscribers',
'Gender Unknown',
'Male',
'Women',
'Customers & Gender Unknown',
'Customers & Male',
'Customers & Woman',
'Subscribers & Gender Unknown',
'Subscribers & Male',
'Subscribers & Woman',
]
return {'data': data, 'labels': labels}
fig, ax = plt.subplots(figsize=(30, 10))
dict_data = get_data_for_boxplot_age2020(80)
_ = ax.boxplot(dict_data['data'], vert=True, showmeans=True, labels=dict_data['labels'], showfliers=False)
ax.set_ylabel('Jahre')
ax.set_title('Vergleich Age 2020 (nur Alter <= 80 Jahre)')
plt.grid(b=True)
#plt.tight_layout()
plt.show()
Im Vergleich ist ersichtlich:
Untersuchung der Anzahl Trips nach Alter.
trip_count_customers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Customer'].groupby(by=['Age 2020', 'Gender'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration':'Trip count'}, axis=1)
trip_count_customers_groupby_age = trip_count_customers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Trip count')
trip_count_customers_groupby_age.columns = ['unknown', 'male', 'female']
#trip_count_customers_groupby_age.head()
trip_count_subscribers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Subscriber'].groupby(by=['Age 2020', 'Gender'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration':'Trip count'}, axis=1)
trip_count_subscribers_groupby_age = trip_count_subscribers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Trip count')
trip_count_subscribers_groupby_age.columns = ['unknown', 'male', 'female']
#trip_count_subscribers_groupby_age.head()
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(20, 8), sharex=True, sharey=False)
trip_count_customers_groupby_age.plot(ax=ax[0], kind='bar', stacked=True, width=0.9)
ax[0].set_axisbelow(False)
ax[0].yaxis.grid(linestyle='dashed')
ax[0].xaxis.grid(linestyle='dashed')
ax[0].set_ylabel('Trip Count')
ax[0].set_title('Customers - Anzahl Trips nach Alter')
trip_count_subscribers_groupby_age.plot(ax=ax[1], kind='bar', stacked=True, width=0.9)
ax[1].set_axisbelow(False)
ax[1].yaxis.grid(linestyle='dashed')
ax[1].xaxis.grid(linestyle='dashed')
ax[1].set_ylabel('Trip Count')
ax[1].set_title('Subscribers - Anzahl Trips nach Alter')
plt.tight_layout()
plt.show()
In den beiden obenstehenden Grafiken ist folgendes ersichtlich:
Ein paar Untersuchungen zu den Stationen.
df_oct2019[[col for col in df_oct2019.columns if col.endswith('Station Latitude') or col.endswith('Station Longitude') or col.endswith('Station Name') or col == 'Linear Distance']].describe(include='all')
Erkenntnisse aus der Übersicht:
Untersuchung der Distanz der Trips nach Alter.
trip_distance_customers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Customer'].groupby(by=['Age 2020', 'Gender'])['Linear Distance'].agg('sum').reset_index().rename({'Linear Distance':'Linear Distance sum'}, axis=1)
trip_distance_customers_groupby_age = trip_distance_customers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Linear Distance sum')
trip_distance_customers_groupby_age.columns = ['unknown', 'male', 'female']
#trip_distance_customers_groupby_age.head()
trip_distance_subscribers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Subscriber'].groupby(by=['Age 2020', 'Gender'])['Linear Distance'].agg('sum').reset_index().rename({'Linear Distance':'Linear Distance sum'}, axis=1)
trip_distance_subscribers_groupby_age = trip_distance_subscribers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Linear Distance sum')
trip_distance_subscribers_groupby_age.columns = ['unknown', 'male', 'female']
#trip_distance_subscribers_groupby_age.head()
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(20, 8), sharex=True, sharey=False)
trip_distance_customers_groupby_age.plot(ax=ax[0], kind='bar', stacked=True, width=0.9)
ax[0].set_axisbelow(False)
ax[0].yaxis.grid(linestyle='dashed')
ax[0].xaxis.grid(linestyle='dashed')
ax[0].set_ylabel('Linear Distance (km)')
ax[0].set_title('Customers - Anzahl Kilometer nach Alter')
trip_distance_subscribers_groupby_age.plot(ax=ax[1], kind='bar', stacked=True, width=0.9)
ax[1].set_axisbelow(False)
ax[1].yaxis.grid(linestyle='dashed')
ax[1].xaxis.grid(linestyle='dashed')
ax[1].set_ylabel('Linear Distance (km)')
ax[1].set_title('Subscribers - Anzahl Kilometer nach Alter')
plt.tight_layout()
plt.show()
In den beiden obenstehenden Grafiken ist folgendes ersichtlich:
Hier soll untersucht werden, welche Stationen am meisten benutzt werden.
starts_count = df_oct2019.groupby(by=['Start Station Name'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration' :'Starts count', 'Start Station Name': 'Station Name'}, axis=1).set_index('Station Name')
#starts_count.head()
ends_count = df_oct2019.groupby(by=['End Station Name'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration' :'Ends count', 'End Station Name': 'Station Name'}, axis=1).set_index('Station Name')
#ends_count.head()
start_stations = df_oct2019[[col for col in df_oct2019.columns if col.startswith('Start Station')]].rename({'Start Station ID': 'Station ID', 'Start Station Name': 'Station Name', 'Start Station Latitude': 'Latitude', 'Start Station Longitude': 'Longitude'}, axis=1).set_index('Station Name')
start_stations = start_stations[start_stations.duplicated() == False]
#start_stations
end_stations = df_oct2019[[col for col in df_oct2019.columns if col.startswith('End Station')]].rename({'End Station ID': 'Station ID', 'End Station Name': 'Station Name', 'End Station Latitude': 'Latitude', 'End Station Longitude': 'Longitude'}, axis=1).set_index('Station Name')
end_stations = end_stations[end_stations.duplicated() == False]
#end_stations
stations = pd.concat([start_stations, end_stations])
stations = stations[stations.duplicated() == False]
#stations
trip_counts_by_station = pd.concat([starts_count, ends_count, stations], axis=1)
trip_counts_by_station.insert(2, 'Trip count', trip_counts_by_station[['Starts count', 'Ends count']].sum(axis=1))
trip_counts_by_station = trip_counts_by_station.sort_values(by='Trip count', ascending=False)
trip_counts_by_station.index.name = 'Station Name'
#trip_counts_by_station.head()
fig, ax = plt.subplots(figsize=(20, 8))
trip_counts_by_station[0:50][['Starts count', 'Ends count']].plot(ax=ax, kind='bar', stacked=True, width=0.9)
ax.set_axisbelow(False)
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.set_ylabel('Trip count')
ax.set_title('Top 50 Stationen')
plt.tight_layout()
plt.show()
Die 50 Stationen mit den meisten Trips wurden in einem Barplot dargestellt.
Die Top 50 Stationen werden noch auf einer Map dargestellt. Dabei stellt die Grösse und die Farbe des Kreises die Anzahl Trips dar.
import tilemapbase
tilemapbase.init(create=True)
tiles_osm = tilemapbase.tiles.Carto_Light
#center_nyc = (-73.967419961490, 40.73100351879932)
center_nyc = (-73.984803, 40.745687)
degree_range_heigth = 0.07
degree_range_width = 0.06
extent = tilemapbase.Extent.from_lonlat(
center_nyc[0] - degree_range_heigth, center_nyc[0] + degree_range_heigth,
center_nyc[1] - degree_range_width, center_nyc[1] + degree_range_width)
extent = extent.to_aspect(1.0)
projection = trip_counts_by_station[0:50].apply(lambda row: tilemapbase.project(latitude=row['Latitude'], longitude=row['Longitude']), axis=1)
x_coords = []
y_coords =[]
names = []
counts = []
for idx, val in enumerate(projection.values):
x_coords.append(val[0])
y_coords.append(val[1])
names.append(trip_counts_by_station[0:50].reset_index()['Station Name'][idx])
counts.append(trip_counts_by_station[0:50]['Trip count'][idx])
fig, ax = plt.subplots(figsize=(10, 10), dpi=150)
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
plotter = tilemapbase.Plotter(extent, tiles_osm, width=600)
plotter.plot(ax, tiles_osm)
sc = ax.scatter(x_coords, y_coords, c=counts, s=counts, cmap=plt.cm.get_cmap('viridis'), alpha=0.9, linewidths=3)
cbar = plt.colorbar(sc, fraction=0.046, pad=0.02, label='Anzahl Trips', orientation='vertical')
ax.set_title('Top 50 Stationen')
#plt.tight_layout()
plt.show()
Auf der Karte ist die Station 'Pershing Square North' mit einem gelben Punkt markiert, diese Station hat auch die meisten Trips. Diese Station liegt am Grand Central Terminal, was vermutlich auch der Grund für die stärkere Auslastung ist.
Untersuchungen zur Temperatur.
df_oct2019['temp'].describe()
_ = show_hist(df_oct2019['temp'], '°C', 'Anzahl', 'Temperatur im Oktober 2019')
Wie oben bei der Übersicht mit den Zahlen ersichtlich, häufen sich im Histogramm die Werte zwischen 14 und 18°C.
_ = show_box(df_oct2019['temp'], '°C', 'Temperatur im Oktober 2019', labels=['temp'])
Im Boxplot ist auch die Häufung zwischen 14 und 18°C sofort ersichtlich. Auch sieht man die Ausreisser, welche bedeuten, dass manche Trips bei einer Temperatur von z.B. über 30°C gefahren wurden.
Damit eine Aussage darüber gemacht werden kann, wie die Temperatur an welchen Tagen war, wird ein Plot über den Monat Oktober 2019 erstellt.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_oct2019.sort_values('Start Time')[['Start Time', 'temp']].set_index('Start Time').plot(ax=ax, y='temp')
ax.set_xlabel('Start Time')
ax.set_ylabel('°C')
ax.set_title('Temperatur im Oktober 2019')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d"))
ax.set_ylim(0, 35)
ax.set_xlim([datetime(2019, 10, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 11, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])
ax.tick_params(axis='x', labelrotation=90)
for tick in ax.xaxis.get_major_ticks():
tick.label.set_horizontalalignment('left')
#plt.tight_layout()
plt.show()
Diese beiden Tage werden in den nächsten beiden Kapiteln noch in einem eigenen Tagesverlauf dargestellt.
df_oct2019[['Start Time', 'temp']].sort_values('temp', ascending=False).head(1)
Der Tag mit der höchsten Temperatur ist der 2.10.2019.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_oct2019[['Start Time', 'temp']].set_index('Start Time')['2019-10-02'].plot(ax=ax, y='temp')
ax.set_xlabel('Start Time')
ax.set_ylabel('°C')
ax.set_title('Temperatur am 2.10.2019')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.xaxis.set_major_locator(mdates.HourLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
ax.set_ylim(0, 35)
ax.set_xlim([datetime(2019, 10, 2, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 10, 3, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])
ax.tick_params(axis='x', labelrotation=90)
for tick in ax.xaxis.get_major_ticks():
tick.label.set_horizontalalignment('left')
#plt.tight_layout()
plt.show()
Die Höchsttemperatur von 33.1°C wurde gegen 15 Uhr erreicht. Die anderen Trips wurden immer mit Temperaturen über 22 Grad gefahren, erst Abends ab ca. 22:30 fiel die Temperatur unter 22 °C.
df_oct2019[['Start Time', 'temp']].sort_values('temp', ascending=True).head(1)
Der Tag mit der tiefsten Temperatur ist der 19.10.2019.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_oct2019[['Start Time', 'temp']].set_index('Start Time')['2019-10-19'].plot(ax=ax, y='temp')
ax.set_xlabel('Start Time')
ax.set_ylabel('°C')
ax.set_title('Temperatur am 19.10.2019')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.xaxis.set_major_locator(mdates.HourLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
ax.set_ylim(0, 20)
ax.set_xlim([datetime(2019, 10, 19, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 10, 20, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])
ax.tick_params(axis='x', labelrotation=90)
for tick in ax.xaxis.get_major_ticks():
tick.label.set_horizontalalignment('left')
#plt.tight_layout()
plt.show()
Die Tiefsttemperatur von 6.1°C wurde um 7 Uhr erreicht. Danach wurde es wieder etwas wärmer.
Untersuchungen zum Niederschlag.
_ = show_hist(df_oct2019['rain_1h'], 'mm', 'Anzahl', 'Niederschlag im Oktober 2019')
Im Histogramm ist nur ersichtlich das die Werte sich in der Nähe von 0 häufen, daher noch ein Histogramm mit logarithmischer y-Achse.
_ = show_hist(df_oct2019['rain_1h'], 'mm', 'Anzahl', 'Niederschlag im Oktober 2019', log=True)
Hier lässt sich herauslesen, dass es meistens bis zu 2mm Niederschlag gab und manchmal auch mehr. Aber an welchen Tagen es im Oktober 2019 geregnet hat, lässt sich nicht erkennen.
_ = show_box(df_oct2019['rain_1h'], 'mm', 'Niederschlag im Oktober 2019', labels=['rain_1h'])
Der Boxplot zeigt das gleiche Bild wie das Histogramm, d.h. der Median liegt bei 0mm, der Durchschnitt etwas über 0mm und alle anderen Werte werden als Ausreisser markiert.
Die Übersicht in Zahlen zum Merkmal rain_1h:
df_oct2019['rain_1h'].describe()
Damit nun auch ersichtlich ist, an welchen Tagen es wie viel Niederschlag gab, wird ein Plot über den ganzen Monat erstellt.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_oct2019.sort_values('Start Time')[['Start Time', 'rain_1h']].plot(ax=ax, x='Start Time', y='rain_1h')
ax.set_xlabel('Start Time')
ax.set_ylabel('mm')
ax.set_title('Niederschlag im Oktober 2019')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d"))
ax.set_xlim([datetime(2019, 10, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 11, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])
ax.tick_params(axis='x', labelrotation=90)
for tick in ax.xaxis.get_major_ticks():
tick.label.set_horizontalalignment('left')
#plt.tight_layout()
plt.show()
Am meisten Niederschlag von 7.81mm gab es am 27. Oktober ca. gegen Mittag. Und am 16. Oktober hat es auch etwas über 7mm Niederschlag gegen Abend gegeben.
summary-daily-subscribers_only-citibike-tripweather.parquet¶Hier werden ein paar Untersuchungen zur täglichen Zusammenfassung gemacht. Zu beachten ist, dass diese Zusammenfassung nur Informationen über Subscriber enthält und über alle Jahre gemacht wurde!
In diesem Kapitel wird nicht mehr alles so detailliert beschrieben wie für den Oktober 2019!
df_sumsubs = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-citibike-tripweather.parquet'))
df_sumsubs.tail().T
| Statistische Einheit | Merkmal | Skalenniveau | Kontinuität | Beschreibung |
|---|---|---|---|---|
| Day | Date | Intervall | diskret | Datum |
| Trip | Trip count | Verhältnis | diskret | Gesamte Anzahl Trips an diesem Tag |
| Trip | Trip Duration mean | Verhältnis | stetig | Durchschnittliche Dauer |
| Trip | Trip Duration std | Verhältnis | stetig | Standardabweichung der Dauer |
| Trip | Trip Duration min | Verhältnis | stetig | Dauer des schnellsten Trips |
| Trip | Trip Duration median | Verhältnis | stetig | Median der Dauer |
| Trip | Trip Duration max | Verhältnis | stetig | Dauer des längsten Trips |
| Trip | Linear Distance mean | Verhältnis | stetig | Durchschnittliche Distanz (Luftlinie zwischen Stationen) |
| Trip | Linear Distance std | Verhältnis | stetig | Standardabweichung der Distanz |
| Trip | Linear Distance min | Verhältnis | stetig | Distanz des kürzesten Trips |
| Trip | Linear Distance median | Verhältnis | stetig | Median der Distanz |
| Trip | Linear Distance max | Verhältnis | stetig | Distanz des weitesten Trips |
| User | Age 2020 count | Verhältnis | diskret | Gesamte Anzahl angegebener Alter |
| User | Age 2020 mean | Verhältnis | stetig | Durchschnittliches Alter |
| User | Age 2020 std | Verhältnis | diskret | Standardabweichung des Alters |
| User | Age 2020 min | Verhältnis | diskret | Jüngstes Alter |
| User | Age 2020 median | Verhältnis | stetig | Median des Alters |
| User | Age 2020 max | Verhältnis | diskret | Ältestes Alter |
| Temperature | temp mean | Intervall | stetig | Durchschnittliche Temperatur (°C) |
| Temperature | temp std | Intervall | stetig | Standardabweichung der Temperatur (°C) |
| Temperature | temp median | Intervall | stetig | Median der Temperatur (°C) |
| Temperature | temp min | Intervall | stetig | Tiefste Temperatur (°C) |
| Temperature | temp max | Intervall | stetig | Höchste Temperatur (°C) |
| Wind | wind_speed mean | Verhältnis | stetig | Durchschnittliche Windgeschwindigkeit (m/s) |
| Wind | wind_speed std | Verhältnis | stetig | Standardabweichung der Windgeschwindigkeit (m/s) |
| Wind | wind_speed median | Verhältnis | stetig | Median der Windgeschwindigkeit (m/s) |
| Wind | wind_speed min | Verhältnis | stetig | Tiefste Windgeschwindigkeit (m/s) |
| Wind | wind_speed max | Verhältnis | stetig | Höchste Windgeschwindigkeit (m/s) |
| Rainfall | rain_1h sum | Verhältnis | stetig | Gesamte Menge an Niederschlag (mm) |
| Rainfall | rain_1h min | Verhältnis | stetig | Tiefste Menge an Niederschlag in 1h am Tag (mm) |
| Rainfall | rain_1h max | Verhältnis | stetig | Höchste Menge an Niederschlag in 1h am Tag (mm) |
| Snowfall | snow_1h sum | Verhältnis | stetig | Gesamte Menge an Schneefall (mm) |
| Snowfall | snow_1h min | Verhältnis | stetig | Tiefste Menge an Schneefall in 1h am Tag (mm) |
| Snowfall | snow_1h max | Verhältnis | stetig | Höchste Menge an Schneefall in 1h am Tag (mm) |
Eine Zusammenfassung der Trips von Subscribern über alle Jahre in Zahlen.
df_sumsubs['Trip count'].describe()
subsubs_trip_count_mean_pm_std = df_sumsubs['Trip count'][
(df_sumsubs['Trip count'] > (df_sumsubs['Trip count'].mean() - df_sumsubs['Trip count'].std())) &
(df_sumsubs['Trip count'] < (df_sumsubs['Trip count'].mean() + df_sumsubs['Trip count'].std()))
].count()
subsubs_trip_count_mean_pm_std_percent = subsubs_trip_count_mean_pm_std / df_sumsubs['Trip count'].count()
print('Anzahl der Werte, die im Bereich mean+-std liegen: {:.2f}% ({} Tage)'.format(subsubs_trip_count_mean_pm_std_percent*100, subsubs_trip_count_mean_pm_std))
Anhand der Zahlen erkennt man, dass
Es folgt das Histogramm dazu.
n, bins, patches = show_hist(df_sumsubs['Trip count'], 'Anzahl Trips', 'Anzahl', 'Trips von Subscribern über alle Jahre')
print('Grenzen: {}, Anzahl: {}'.format(bins, n))
Das Histogramm zeigt eine Häufung in den Klassen die zwischen 20000 und 40000 Trips liegen. Die Grenzen der höchsten Klasse liegen zwischen 29495.55 und 33587.2 Trips.
Und der Boxplot zu den gleichen Daten.
boxplot = show_box(df_sumsubs['Trip count'], 'Anzahl Trips', 'Trips von Subscribern über alle Jahre', labels=['Trip count'])
Alle Daten liegen innerhalb der unauffälligen Streuung, d.h. es gibt keine Ausreisser. Ansonsten zeigt der Boxplot die Erkenntnisse die oben schon mit den Zahlen gewonnen wurden.
Es wird eine monatliche Aggregation der Anzahl der Trips erstellt und in einem Plot dargestellt.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
_ = df_sumsubs['Trip count'].groupby(pd.Grouper(freq='M')).sum().plot(ax=ax, y='Trip count')
ax.set_xlabel('Jahr')
ax.set_ylabel('Anzahl')
ax.set_title('Anzahl Trips (Summe pro Monat)')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.ticklabel_format(axis='y', style='plain')
ax.get_yaxis().set_major_formatter(
matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plt.show()
Hier ist erkennbar, dass
Ein paar Untersuchungen zur Windgeschwindigkeit.
In folgender Tabelle ist eine Übersicht über die Windgeschwindigkeit über alle Jahre aufgelistet.
df_sumsubs_wind_speed = df_sumsubs[[col for col in df_sumsubs.columns if col.startswith('wind_speed') & (col.endswith('mean') | col.endswith('median') | col.endswith('min') | col.endswith('max'))]]
df_sumsubs_wind_speed.describe()
Zur einfacheren Interpretation wird die Windgeschwindigkeit noch in km/h umgerechnet.
df_sumsubs_wind_speed_kmh = df_sumsubs_wind_speed / 1000 *60 * 60
df_sumsubs_wind_speed_kmh.describe()
Interpretation der Zahlen:
fig = plt.figure(figsize=(20, 5))
data_wind_speed_kmh_mean = df_sumsubs_wind_speed_kmh['wind_speed mean'].groupby(pd.Grouper(freq='M')).mean()
data_wind_speed_kmh_median = df_sumsubs_wind_speed_kmh['wind_speed median'].groupby(pd.Grouper(freq='M')).median()
data_wind_speed_kmh_min = df_sumsubs_wind_speed_kmh['wind_speed min'].groupby(pd.Grouper(freq='M')).min()
data_wind_speed_kmh_max = df_sumsubs_wind_speed_kmh['wind_speed max'].groupby(pd.Grouper(freq='M')).max()
ax = fig.add_subplot()
_ = ax.plot(data_wind_speed_kmh_mean.index, data_wind_speed_kmh_mean, label='wind_speed mean of daily mean')
_ = ax.plot(data_wind_speed_kmh_median.index, data_wind_speed_kmh_median, label='wind_speed median of daily median')
_ = ax.plot(data_wind_speed_kmh_min.index, data_wind_speed_kmh_min, label='wind_speed min')
_ = ax.plot(data_wind_speed_kmh_max.index, data_wind_speed_kmh_max, label='wind_speed max')
ax.set_xlabel('Jahr')
ax.set_ylabel('km/h')
ax.set_title('Windgeschwindigkeit')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
plt.legend()
plt.show()
In der Grafik ist ersichtlich, dass
Die durchschnittliche Windgeschwindigkeit pro Tag, wird in einem Boxplot, gruppiert nach Jahren, verglichen.
ax = (
df_sumsubs_wind_speed_kmh
.set_index(df_sumsubs_wind_speed_kmh.index.year)['wind_speed mean']
.reset_index()
.pivot(columns='Date', values='wind_speed mean')
.apply(lambda x: pd.Series(x.dropna().values))
.boxplot(vert=False, figsize=(20, 10), widths=0.7, showmeans=True)
)
ax.set_xlabel('km/h')
ax.set_ylabel('Jahr')
ax.set_title('Durchschnittliche Windgeschwindigkeit pro Tag')
plt.show()
Hier ist ersichtlich, dass
Die maximale Windgeschwindigkeit pro Tag, wird in einem Boxplot, gruppiert nach Jahren, verglichen.
ax = (
df_sumsubs_wind_speed_kmh
.set_index(df_sumsubs_wind_speed_kmh.index.year)['wind_speed max']
.reset_index()
.pivot(columns='Date', values='wind_speed max')
.apply(lambda x: pd.Series(x.dropna().values))
.boxplot(vert=False, figsize=(20, 10), widths=0.7, showmeans=True)
)
ax.set_xlabel('km/h')
ax.set_ylabel('Jahr')
ax.set_title('Maximale Windgeschwindigkeit pro Tag')
plt.show()
Schaut man sich die maximale Windgeschwindigkeit pro Tag an, dann ist ersichtlich, dass
summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet¶Hier werden ein paar Untersuchungen zur täglichen Zusammenfassung gemacht. Zu beachten ist, dass diese Zusammenfassung nur Informationen über Subscriber enthält und über alle Jahre gemacht wurde! Zusätzlich wurde auch eine Gruppierung nach Geschlecht vorgenommen.
In diesem Kapitel wird nicht mehr alles so detailliert beschrieben wie für den Oktober 2019!
df_sumsubs_bygen = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet'))
df_sumsubs_bygen.tail().T
Zusätzlich zu den Merkmalen aus summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet, gibt es noch zusätzlich das folgende Merkmal:
| Statistische Einheit | Merkmal | Skalenniveau | Beschreibung |
|---|---|---|---|
| User | Gender | Nominal | Geschlecht des Benutzers: 0=unknown; 1=male; 2=female |
df_sumsubs_bygen.groupby(by='Gender')['Trip count'].describe().T
Die Übersicht zeigt, dass
def get_data_trip_count_all_years():
data = [
df_sumsubs_bygen[df_sumsubs_bygen['Gender'] == '0']['Trip count'],
df_sumsubs_bygen[df_sumsubs_bygen['Gender'] == '1']['Trip count'],
df_sumsubs_bygen[df_sumsubs_bygen['Gender'] == '2']['Trip count']
]
labels = [
'Subscriber, unknown',
'Subscriber, male',
'Subscriber, female'
]
return { 'data': data, 'labels': labels }
fig, ax = plt.subplots(figsize=(20, 5))
dict_data = get_data_trip_count_all_years()
_ = ax.boxplot(dict_data['data'], vert=False, showmeans=True, labels=dict_data['labels'], widths=0.8, showfliers=True)
ax.set_xlabel('Anzahl Trips')
ax.set_title('Tägliche Trips über alle Jahre')
ax.set_axisbelow(True)
ax.xaxis.grid(linestyle='dashed')
ax.get_xaxis().set_major_formatter(
matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
#plt.tight_layout()
plt.show()
Im Boxplot ist sofort ersichtlich das die Männer die meisten Trips gefahren sind. Sie weisen aber auch die höchste Streuung aus. Am wenigsten Trips wurden von Subscribern gemacht die ihr Geschlecht nicht angegeben haben.
fig = plt.figure(figsize=(20, 5))
ax = fig.add_subplot()
dict_data = get_data_trip_count_all_years()
for idx, data in enumerate(dict_data['data']):
data_agg_monthly = data.groupby(pd.Grouper(freq='W')).sum()
_ = ax.plot(data_agg_monthly.index, data_agg_monthly, label=dict_data['labels'][idx])
ax.set_xlabel('Jahr')
ax.set_ylabel('Anzahl Trips')
ax.set_title('Wöchentliche Anzahl Trips')
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.ticklabel_format(axis='y', style='plain')
ax.get_yaxis().set_major_formatter(
matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
#ax.tick_params(axis='x', labelrotation=90)
plt.legend()
plt.show()
In obiger Grafik ist das gleiche Verhalten ersichtlich wie schon beschrieben. Hier ist auch ersichtlich das es einen steigenden Trend gibt und immer mehr Trips gefahren werden. Im 2020 scheint es aber einen stärkeren Einbruch zu geben, dies vermutlich aufgrund der weltweiten Pandemie.
def numcols_hist(df):
'''
Plots all histograms of numeric columns in the DataFrame.
'''
numeric_columns = []
for col in df.columns:
if df[col].dtype == np.float64 or df[col].dtype == np.int64 or isinstance(df[col].dtype, pd.Int64Dtype):
numeric_columns.append(col)
fig = plt.figure(figsize=(20, len(numeric_columns) * 2))
gs = gridspec.GridSpec(len(numeric_columns), 2, figure=fig)
for idx, col in enumerate(numeric_columns):
ax = fig.add_subplot(gs[idx, 0])
ax.set_title(col)
_ = df[col].hist(ax=ax, bins=25)
ax = fig.add_subplot(gs[idx, 1])
ax.set_title(col + ' (log)')
_ = df[col].hist(ax=ax, bins=25)
ax.set_yscale('log')
fig.tight_layout()
plt.show()
def numcols_box(df):
'''
Plots all boxplots of numeric columns in the DataFrame.
'''
numeric_columns = []
for col in df.columns:
if df[col].dtype == np.float64 or df[col].dtype == np.int64 or isinstance(df[col].dtype, pd.Int64Dtype):
numeric_columns.append(col)
fig = plt.figure(figsize=(20, len(numeric_columns) * 2))
gs = gridspec.GridSpec(len(numeric_columns), 1, figure=fig)
for idx, col in enumerate(numeric_columns):
ax = fig.add_subplot(gs[idx, 0])
ax.set_title(col)
_ = ax.boxplot(df[col], vert=False, showmeans=True, widths=0.7)
fig.tight_layout()
plt.show()
samples_5000_201910-citibike-tripweather-data.parquet¶df_oct2019_attachment = pd.read_parquet(os.path.join(path, 'samples_5000_201910-citibike-tripweather-data.parquet'))
df_oct2019_attachment.describe(include='all').T
%%time
numcols_hist(df_oct2019_attachment)
%%time
numcols_box(df_oct2019_attachment)
Es sind sehr viele Merkmale! Daher dauert die Berechnung auf nicht performanten Rechnern einige Zeit!
Es werden aufgrund der Performance und der Übersichtlichkeit hier nur einige Merkmale in den Plot mit einbezogen!
Auch werden nur Trips mit einer Dauer von maximal 33.1 Minuten berücksichtigt und auch nur eine zufällige Stichprobe von 500 Beobachtungen dargestellt.
number_of_samples = 500
df_oct2019_attachment.columns
columns = ['Trip Duration', 'Age 2020', 'temp', 'wind_speed', 'rain_1h']
%%time
_ = sns.pairplot(df_oct2019_attachment[df_oct2019_attachment['Trip Duration'] <= 1*60*33.1][columns].sample(number_of_samples, random_state=SEED), height=3)
plt.show()
Auf den ersten Blick sind hier nirgendwo spezielle Zusammenhänge zu erkennen.
summary-daily-subscribers_only-citibike-tripweather.parquet¶df_sumsubs_attachment = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-citibike-tripweather.parquet'))
df_sumsubs_attachment.describe(include='all').T
%%time
numcols_hist(df_sumsubs_attachment)
%%time
numcols_box(df_sumsubs_attachment)
Es sind sehr viele Merkmale! Daher dauert die Berechnung auf nicht performanten Rechnern einige Zeit!
Es werden aufgrund der Performance und der Übersichtlichkeit hier nur einige Merkmale in den Plot mit einbezogen!
df_sumsubs_attachment.columns
columns = ['Trip count', 'Trip Duration mean', 'temp mean', 'wind_speed mean', 'rain_1h sum', 'snow_1h sum']
%%time
_ = sns.pairplot(df_sumsubs_attachment[columns], height=3)
plt.show()
Es scheint das z.B. bei 'temp mean' und 'Trip Duration mean' ein scheinbarer, linearer Zusammenhang bestehen könnte.
summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet¶df_sumsubs_bygen_attachment = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet'))
df_sumsubs_bygen_attachment.describe(include='all').T
%%time
numcols_hist(df_sumsubs_bygen_attachment)
%%time
numcols_box(df_sumsubs_bygen_attachment)
Es sind sehr viele Merkmale! Daher dauert die Berechnung auf nicht performanten Rechnern einige Zeit!
Es werden aufgrund der Performance und der Übersichtlichkeit hier nur einige Merkmale in den Plot mit einbezogen!
Auch werden immer nur 250 zufällig gewählte Stichproben dargestellt!
number_of_samples = 250
columns = ['Gender', 'Trip count', 'Trip Duration mean', 'temp mean', 'wind_speed mean', 'rain_1h sum', 'snow_1h sum']
%%time
_ = sns.pairplot(df_sumsubs_bygen_attachment[columns].sample(number_of_samples), height=3, hue='Gender')
plt.show()
Man sieht, dass die Beobachtungen mit fehlendem Geschlecht (0, unknown) die Plots etwas unübersichtlich machen und Konzentrationen entstehen. Die Werte sind quasi Ausreisser. Daher wird nochmal eine Scatterplot Matrix erstellt ohne 0, 'unknown'.
%%time
_ = sns.pairplot(df_sumsubs_bygen_attachment[columns]
[(df_sumsubs_bygen_attachment['Gender'] == '1') | (df_sumsubs_bygen_attachment['Gender'] == '2')]
.sample(number_of_samples, random_state=SEED), height=3, hue='Gender')
plt.show()
Hier fällt auf, dass es einen linearen Zusammenhang zu geben scheint zwischen 'Trip count' und 'temp mean' der bei den Frauen ausgeprägter ist als bei den Männern. Auch 'Trip Duration mean' und 'temp mean' scheinen einen Zusammenhang aufzuweisen.